import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { Observable, concat } from 'rxjs';

@Component({
  selector: 'app-renderer',
  templateUrl: './renderer.component.html',
  styleUrls: ['./renderer.component.scss']
})
export class RendererComponent implements OnInit {
  notSupported: boolean;
  @ViewChild('renderer') private _refRenderer: ElementRef;

  // WebGL vars

  constructor() { }
  ngOnInit(): void {
    console.log(this.renderer);
    this.render();
  }

  render() {
    const gl = this.getWebGL();
    if (!gl) return;
    let vertShader: WebGLShader, fragShader: WebGLShader;
    concat(
      this.loadShader(gl, gl.VERTEX_SHADER, 'assets/shaders/vert.glsl'),
      this.loadShader(gl, gl.FRAGMENT_SHADER, 'assets/shaders/frag.glsl')
    )
      .subscribe(shader => {
        console.log(shader);
        if (!vertShader) {
          vertShader = shader;
        } else if (!fragShader) {
          fragShader = shader;
        }
      },
        err => console.error('Render failed:', err),
        () => {
          const prog = this.createProgram(gl, vertShader, fragShader);
          this.draw(gl, prog);
        });
  }

  resize(gl: WebGLRenderingContext) {
    const pxRatio = window.devicePixelRatio;
    const displayWidth = Math.floor(gl.canvas.clientWidth * pxRatio);
    const displayHeight = Math.floor(gl.canvas.clientHeight * pxRatio);
    if (gl.canvas.width !== displayWidth || gl.canvas.height !== displayHeight) {
      gl.canvas.width = displayWidth;
      gl.canvas.height = displayHeight;
    }
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  }

  draw(gl: WebGLRenderingContext, prog: WebGLProgram) {
    // ---------------- Initialize ----------------
    // Uniforms
    const uniLocResolution = gl.getUniformLocation(prog, 'u_resolution');
    const uniLocColor = gl.getUniformLocation(prog, 'u_color');
    const color = [100 / 255, 150 / 255, 200 / 255, 255];
    // Attributes
    const attrLocPosition = gl.getAttribLocation(prog, 'a_position');
    const bufPosition = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, bufPosition);
    // const positions = [
    //   100, 200,
    //   800, 200,
    //   100, 600,
    //   100, 600,
    //   800, 200,
    //   800, 600,
    // ];
    // gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
    this.setRectangle(gl, 100, 100, 1000, 800);

    // ---------------- Rendering ----------------
    this.resize(gl);
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(prog);
    // Upload uniforms
    gl.uniform2f(uniLocResolution, gl.canvas.width, gl.canvas.height);
    gl.uniform4fv(uniLocColor, new Float32Array(color));
    // Upload attributes
    gl.enableVertexAttribArray(attrLocPosition);
    gl.bindBuffer(gl.ARRAY_BUFFER, bufPosition);
    const size = 2;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.vertexAttribPointer(attrLocPosition, size, type, normalize, stride, offset);
    const primitive = gl.TRIANGLES;
    const vertexOffset = 0;
    const vertexCount = 6;
    gl.drawArrays(primitive, vertexOffset, vertexCount);
  }

  get renderer() {
    if (!this._refRenderer) return null;
    return this._refRenderer.nativeElement as HTMLCanvasElement;
  }
  getWebGL() {
    if (!this.renderer) {
      console.error('Cannot find the renderer canvas');
      return null;
    }
    const gl = this.renderer.getContext('webgl') || this.renderer.getContext('experimental-webgl');
    if (!gl) {
      this.notSupported = true;
      console.error('WebGL is not support. Are you from 1990s?');
      return null;
    }
    this.notSupported = false;
    return gl;
  }

  loadShader(gl: WebGLRenderingContext, type: number, url: string) {
    return new Observable(observer => {
      if (!gl) {
        observer.error('Loading shader failed: WebGL not initialized');
      }
      const xhr = new XMLHttpRequest();
      xhr.open('GET', url);
      xhr.send();
      xhr.onload = e => {
        if (xhr.status !== 200) {
          observer.error(`failed to load shader @ ${url}: ${xhr.statusText}(${xhr.status})`);
        } else {
          const shader = this.createShader(gl, type, xhr.responseText);
          if (shader) {
            observer.next(shader);
            observer.complete();
          } else {
            observer.error('Shader compiling failed');
          }
        }
      };
      return () => xhr.abort();
    });
  }
  createShader(gl: WebGLRenderingContext, type: number, source: string) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    const succ = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (succ) {
      return shader;
    }
    console.error(`Failed to compile shader: ${source}`, gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  createProgram(gl: WebGLRenderingContext, vertShader: WebGLShader, fragShader: WebGLShader) {
    if (!vertShader || !fragShader) {
      console.error('Not all shaders are loaded', `vert: ${!!vertShader}, frag: ${!!fragShader}`);
      return;
    }
    const prog = gl.createProgram();
    gl.attachShader(prog, vertShader);
    gl.attachShader(prog, fragShader);
    gl.linkProgram(prog);
    const succ = gl.getProgramParameter(prog, gl.LINK_STATUS);
    if (succ) return prog;
    console.error(`Failed to create webgl program:`, gl.getProgramInfoLog(prog));
    gl.deleteProgram(prog);
  }
  setRectangle(gl: WebGLRenderingContext, x: number, y: number, width: number, height: number) {
    const min = [x, y];
    const max = [x + width, y + height];
    const rectVerts = new Float32Array([
      min[0], min[1],
      min[0], max[1],
      max[0], min[1],
      max[0], min[1],
      min[0], max[1],
      max[0], max[1],
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, rectVerts, gl.STATIC_DRAW);
  }
}
